/* Complet (Autocompletion through Graph Databases)
 *  Copyright (C) 2012  Paul Wagner, Till Speicher
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 3 of the License, or (at your
 *  option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, see <http:/* www.gnu.org/licenses/>.
 */

/*
 * Class for establishing connection to Neo4J database
 *
 * @author Paul Wagner
 */
package me.test.neo4j;

import java.util.HashMap;
import java.util.Map;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexManager;
import org.neo4j.kernel.EmbeddedGraphDatabase;

public class DBConn {

    // PROPERTIES
    public EmbeddedGraphDatabase graph;
    private Transaction tx;
    private DynamicRelationshipType[] dn;
    private Index<Node> wordIndex; // TODO add rel and node buffer
    private String INDEX_KEY = ConfigHelper.NAME_KEY;
    private String CACHE_TYPE = ConfigHelper.CACHE_TYPE;
    private Integer MAX_RELATIONS = ConfigHelper.MAX_RELATIONS;
    private Boolean BUFFERED = false;
//    private DBTraverser traverser = null;

    /*
     * Constructor using ConfigHelper
     */
    public DBConn() {
        this(ConfigHelper.DB_PATH, ConfigHelper.MAX_RELATIONS, ConfigHelper.NAME_KEY, ConfigHelper.CACHE_TYPE, ConfigHelper.BUFFER);
    }

    /*
     * Constructor using manual config values
     *
     */
    public DBConn(String database, int max_relations, String index_key, String cache_type, Boolean buffer) {
        this.INDEX_KEY = index_key;
        this.MAX_RELATIONS = max_relations;
        this.CACHE_TYPE = cache_type;
        this.BUFFERED = buffer;

        Map<String, String> config = new HashMap<String, String>();
        config.put("cache_type", this.CACHE_TYPE);

        IOHelper.log("(DBConn) New Connection");
        this.graph = new EmbeddedGraphDatabase(database, config);
        IndexManager ix = this.graph.index();
        this.wordIndex = ix.forNodes(this.INDEX_KEY);
        this.dn = new DynamicRelationshipType[this.MAX_RELATIONS + 1];
        for (int i = 1; i <= this.MAX_RELATIONS; i++) {
            this.dn[i] = DynamicRelationshipType.withName("rel:" + i);
        }
//        this.traverser = new DBTraverser(this.dn);

        if (this.BUFFERED) {
            bufferDatabase();
        }
    }

    /*
     * Destructor assuring clean DB shutdown
     *
     */
    protected void finalize() {
        closeConnection();
    }

    // GETTER AND SETTER
    public Boolean isBUFFERED() {
        return BUFFERED;
    }

    public String getCACHE_TYPE() {
        return CACHE_TYPE;
    }

    public String getINDEX_KEY() {
        return INDEX_KEY;
    }

    public Integer getMAX_RELATIONS() {
        return MAX_RELATIONS;
    }

    public DynamicRelationshipType[] getDn() {
        return dn;
    }

    public EmbeddedGraphDatabase getGraph() {
        return graph;
    }

    public Transaction getTx() {
        return tx;
    }

    public Index<Node> getWordIndex() {
        return wordIndex;
    }

    /*
     * Open transaction
     *
     */
    public void openTransaction() {
        this.tx = this.graph.beginTx();
    }

    /*
     * Close transaction
     *
     */
    public void closeTransaction() {
        this.tx.success();
        this.tx.finish();
    }

    /*
     * Commit transaction
     *
     */
    public void commitTransaction() {
        IOHelper.log("(DBConn) Commit transaction");
        closeTransaction();
        openTransaction();
    }

    /*
     * Close Connection
     *
     */
    public void closeConnection() {
        if (this.graph != null) {
            this.graph.shutdown();
        }
    }

//    /*
//     * Append personalized 4-gram to database
//     */
//    public void appendSentence(String[] words, String uid) {
//        if (words[0].isEmpty() || words.length < 4) {
//            return;
//        }
//
//        Integer cnt = 0;
//
//        // Handle big graph
//        Node startNode = getWordIndex().get(INDEX_KEY, words[0]).getSingle();
//        if (startNode == null) {
//            startNode = getGraph().createNode();
//            startNode.setProperty(ConfigHelper.NAME_KEY, words[0]);
//            startNode.setProperty(ConfigHelper.COUNT_KEY, 0);
//            getWordIndex().add(startNode, INDEX_KEY, words[0]);
//        }
//
//        for (int i = 1; i < words.length; i++) {
//            Node node = getWordIndex().get(INDEX_KEY, words[i]).getSingle();
//            if (node == null) {
//                node = getGraph().createNode();
//                node.setProperty(ConfigHelper.NAME_KEY, words[i]);
//                node.setProperty(ConfigHelper.COUNT_KEY, 0);
//                getWordIndex().add(node, INDEX_KEY, words[i]);
//                Relationship rel = startNode.createRelationshipTo(node, dn[i]);
//                rel.setProperty(ConfigHelper.COUNT_KEY, 0);
//            }
//        }
//
//        if (!sid.isEmpty() && ConfigHelper.PERSONALIZE_DATABASE) {
//            // Create or load sid node
//            Node sidNode = getWordIndex().get(ConfigHelper.SID_KEY, sid).getSingle();
//            if (sidNode == null) {
//                sidNode = getGraph().createNode();
//                sidNode.setProperty(ConfigHelper.SID_KEY, sid);
//                sidNode.setProperty(ConfigHelper.DATE_KEY, new Date());
//                getWordIndex().add(sidNode, ConfigHelper.SID_KEY, sid);
//            } else {
//                sidNode.setProperty(ConfigHelper.DATE_KEY, new Date());
//            }
//
//            // Now do the same for the personalized graph
//            startNode = getWordIndex().get(sid, words[0]).getSingle();
//            if (startNode == null) {
//                startNode = getGraph().createNode();
//                startNode.setProperty(ConfigHelper.SID_KEY, sid);
//                startNode.setProperty(ConfigHelper.NAME_KEY, words[0]);
//                startNode.setProperty(ConfigHelper.COUNT_KEY, 1);
//                getWordIndex().add(startNode, sid, words[0]);
//                sidNode.createRelationshipTo(startNode, null);
//            } else {
//                cnt = (Integer) startNode.getProperty(ConfigHelper.COUNT_KEY);
//                startNode.setProperty(ConfigHelper.COUNT_KEY, cnt + 1);
//            }
//
//            // Now do traversal and append for every word
//            for (int i = 1; i < words.length; i++) {
//                // If there is no node, its easy...
//                Node node = getWordIndex().get(sid, words[i]).getSingle();
//                if (node == null) {
//                    node = getGraph().createNode();
//                    node.setProperty(ConfigHelper.NAME_KEY, words[i]);
//                    node.setProperty(ConfigHelper.SID_KEY, sid);
//                    node.setProperty(ConfigHelper.COUNT_KEY, 1);
//                    getWordIndex().add(node, sid, words[i]);
//                    sidNode.createRelationshipTo(node, null);
//                    Relationship rel = startNode.createRelationshipTo(node, dn[i]);
//                    rel.setProperty(ConfigHelper.COUNT_KEY, 1);
//                } else {
//                    // Otherwise we have to traverse the right path...
//                    Traverser trav = traverser.getSuccessorRel(startNode, i, words[i]);
//                    Relationship rel = trav.currentPosition().lastRelationshipTraversed();
//                    // ...and change things there
//                    cnt = (Integer) rel.getProperty(ConfigHelper.COUNT_KEY);
//                    rel.setProperty(ConfigHelper.COUNT_KEY, cnt + 1);
//                }
//            }
//
//            // After this, we have to norm startNode
//            if (ConfigHelper.NORM_AFTER_APPEND) {
//                locNormNode(startNode, words.length);
//            }
//        }
//    }
//
//    /*
//     * Norm a single Node
//     *
//     */
//    private void locNormNode(Node startNode, int n) {
//        HashMap<Long, Relationship> tmp = new HashMap<Long, Relationship>();
//
//        for (int i = 1; i <= n - 1; i++) {
//            tmp.clear();
//            for (Relationship r : startNode.getRelationships(getDn()[i], Direction.OUTGOING)) {
//                tmp.put(r.getId(), r);
//            }
//
//            int cur = 0;
//            double max_loc = 0;
//            for (Long key : tmp.keySet()) {
//                // Get loc maximum
//                cur = (Integer) tmp.get(key).getProperty(ConfigHelper.COUNT_KEY);
//                if (cur > max_loc) {
//                    max_loc = cur;
//                }
//            }
//            for (Long key : tmp.keySet()) {
//                Relationship rel = tmp.get(key);
//                // Write loc maximum
//                cur = (Integer) rel.getProperty(ConfigHelper.COUNT_KEY);
//                rel.setProperty(ConfigHelper.RELLOC_KEY, cur / max_loc);
//            }
//        }
//        commitTransaction();
//    }

    /*
     * Buffer database
     *
     */
    private void bufferDatabase() {
        IOHelper.log("(DBConn) Start buffering database");
        Long ref = this.graph.getReferenceNode().getId();
        for (Node n : this.graph.getAllNodes()) {
            if (n.getId() != ref) {
                n.getProperty(ConfigHelper.NAME_KEY);
            }
            for (Relationship r : n.getRelationships(Direction.OUTGOING)) {
                if (r.hasProperty(ConfigHelper.RELSUM_KEY)) {
                    r.getProperty(ConfigHelper.RELSUM_KEY);
                }
                if (r.hasProperty(ConfigHelper.COUNT_KEY)) {
                    r.getProperty(ConfigHelper.COUNT_KEY);
                }
                if (r.hasProperty(ConfigHelper.RELLOC_KEY)) {
                    r.getProperty(ConfigHelper.RELLOC_KEY);
                }
                r.getEndNode().getProperty(ConfigHelper.NAME_KEY);
            }
        }
        IOHelper.log("(DBConn) Successful buffered database");
    }
}
